# -*- coding: utf-8 -*-
# Author   : ZhangQing
# Time     : 2025-08-04 23:41
# File     : security.py
# Project  : codebuddy_craft
# Desc     : 安全工具

# backend/utils/security.py
"""
安全工具模块 🔐

提供密码哈希、JWT令牌、权限验证等安全功能
"""

from passlib.context import CryptContext
from jose import JWTError, jwt
from datetime import datetime, timedelta
from typing import Optional, Dict, Any
import secrets
import hashlib
import re
from fastapi import Depends
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

from .config import settings
from .logger import get_logger

logger = get_logger(__name__)

# 密码上下文
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


def verify_password(plain_password: str, hashed_password: str) -> bool:
    """验证密码"""
    try:
        return pwd_context.verify(plain_password, hashed_password)
    except Exception as e:
        logger.error(f"密码验证错误: {str(e)}")
        return False


def get_password_hash(password: str) -> str:
    """生成密码哈希"""
    return pwd_context.hash(password)


def validate_password_strength(password: str) -> Dict[str, Any]:
    """验证密码强度"""
    errors = []

    if len(password) < settings.PASSWORD_MIN_LENGTH:
        errors.append(f"密码长度至少{settings.PASSWORD_MIN_LENGTH}个字符")

    if settings.PASSWORD_REQUIRE_UPPERCASE and not re.search(r'[A-Z]', password):
        errors.append("密码必须包含大写字母")

    if settings.PASSWORD_REQUIRE_LOWERCASE and not re.search(r'[a-z]', password):
        errors.append("密码必须包含小写字母")

    if settings.PASSWORD_REQUIRE_DIGIT and not re.search(r'\d', password):
        errors.append("密码必须包含数字")

    if settings.PASSWORD_REQUIRE_SYMBOL and not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
        errors.append("密码必须包含特殊字符")

    return {
        'is_valid': len(errors) == 0,
        'errors': errors,
        'strength': calculate_password_strength(password)
    }


def calculate_password_strength(password: str) -> str:
    """计算密码强度"""
    score = 0

    # 长度评分
    if len(password) >= 12:
        score += 2
    elif len(password) >= 8:
        score += 1

    # 字符类型评分
    if re.search(r'[a-z]', password):
        score += 1
    if re.search(r'[A-Z]', password):
        score += 1
    if re.search(r'\d', password):
        score += 1
    if re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
        score += 1

    # 复杂度评分
    if len(set(password)) / len(password) > 0.7:  # 字符多样性
        score += 1

    if score <= 2:
        return "弱"
    elif score <= 4:
        return "中"
    elif score <= 6:
        return "强"
    else:
        return "很强"


def create_access_token(data: Dict[str, Any], expires_delta: Optional[timedelta] = None) -> str:
    """创建访问令牌"""
    to_encode = data.copy()

    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)

    to_encode.update({"exp": expire, "type": "access"})

    encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
    return encoded_jwt


def create_refresh_token(data: Dict[str, Any]) -> str:
    """创建刷新令牌"""
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS)

    to_encode.update({"exp": expire, "type": "refresh"})

    encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
    return encoded_jwt


def verify_token(token: str, token_type: str = "access") -> Optional[Dict[str, Any]]:
    """验证令牌"""
    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])

        # 检查令牌类型
        if payload.get("type") != token_type:
            return None

        # 检查过期时间
        exp = payload.get("exp")
        if exp is None or datetime.utcnow() > datetime.fromtimestamp(exp):
            return None

        return payload

    except JWTError as e:
        logger.warning(f"JWT验证失败: {str(e)}")
        return None


def decode_access_token(token: str) -> Optional[str]:
    """
    解码访问令牌并返回用户ID
    
    Args:
        token: JWT访问令牌
        
    Returns:
        成功返回用户ID，失败返回None
    """
    try:
        payload = verify_token(token, "access")
        if not payload:
            return None
            
        # 从令牌中获取用户ID
        user_id = payload.get("sub")
        if not user_id:
            logger.warning("令牌中缺少用户ID")
            return None
            
        return user_id
    except Exception as e:
        logger.error(f"令牌验证错误: {str(e)}")
        return None


def generate_api_key() -> str:
    """生成API密钥"""
    return secrets.token_urlsafe(32)


def generate_reset_token() -> str:
    """生成重置令牌"""
    return secrets.token_urlsafe(32)


def hash_api_key(api_key: str) -> str:
    """哈希API密钥"""
    return hashlib.sha256(api_key.encode()).hexdigest()


def verify_api_key(api_key: str, hashed_key: str) -> bool:
    """验证API密钥"""
    return hash_api_key(api_key) == hashed_key


def sanitize_filename(filename: str) -> str:
    """清理文件名，防止路径遍历攻击"""
    # 移除危险字符
    filename = re.sub(r'[<>:"/\\|?*]', '', filename)

    # 移除路径分隔符
    filename = filename.replace('..', '').replace('/', '').replace('\\', '')

    # 确保不为空
    if not filename.strip():
        filename = "untitled"

    return filename.strip()


def validate_file_type(filename: str, allowed_extensions: list) -> bool:
    """验证文件类型"""
    if not filename:
        return False

    extension = filename.split('.')[-1].lower() if '.' in filename else ''
    return f'.{extension}' in allowed_extensions


def generate_csrf_token() -> str:
    """生成CSRF令牌"""
    return secrets.token_urlsafe(32)


def verify_csrf_token(token: str, expected_token: str) -> bool:
    """验证CSRF令牌"""
    return secrets.compare_digest(token, expected_token)


class RateLimiter:
    """速率限制器"""

    def __init__(self):
        self.requests = {}

    def is_allowed(self, identifier: str, max_requests: int, window_seconds: int) -> bool:
        """检查是否允许请求"""
        now = datetime.utcnow().timestamp()
        window_start = now - window_seconds

        # 清理过期记录
        if identifier in self.requests:
            self.requests[identifier] = [
                req_time for req_time in self.requests[identifier]
                if req_time > window_start
            ]
        else:
            self.requests[identifier] = []

        # 检查请求数量
        if len(self.requests[identifier]) >= max_requests:
            return False

        # 记录当前请求
        self.requests[identifier].append(now)
        return True


# 全局速率限制器
rate_limiter = RateLimiter()


def check_rate_limit(identifier: str, max_requests: int = None, window_seconds: int = None) -> bool:
    """检查速率限制"""
    max_requests = max_requests or settings.RATE_LIMIT_REQUESTS
    window_seconds = window_seconds or settings.RATE_LIMIT_WINDOW

    return rate_limiter.is_allowed(identifier, max_requests, window_seconds)


async def get_current_user_id(credentials: HTTPAuthorizationCredentials = Depends(HTTPBearer())):
    """
    从令牌中获取当前用户ID
    
    Args:
        credentials: HTTP授权凭证
        
    Returns:
        用户ID字符串
        
    Raises:
        HTTPException: 如果令牌无效或用户不存在
    """
    from fastapi import HTTPException, status
    
    if not credentials:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="未提供认证令牌",
            headers={"WWW-Authenticate": "Bearer"},
        )
    
    user_id = decode_access_token(credentials.credentials)
    if not user_id:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="无效的认证令牌",
            headers={"WWW-Authenticate": "Bearer"},
        )
    
    return user_id
